我們之前在喂歷史資料,都是先用 shioaji 下載下來,然後再用 padas 轉成 dataframe,最後再喂給 backtrader,在 backtrader 裡的 datafeeds 也是可以自訂義的,有幾個步驟:
    params = (('dataname', None),
     ('fromdate', datetime.datetime.min),
     ('todate', datetime.datetime.max),
     ('name', ''),
     ('compression', 1),
     ('timeframe', TimeFrame.Days),
     ('sessionend', None))
以下我就用 Shioaji 取得資料來示範
import backtrader as bt
import backtrader.feeds as btfeeds
from backtrader import date2num
import shioaji as sj
from datetime import datetime
class ShioajiFeeds(bt.feed.DataBase):
    params = (
        ("person_id", "身份證字號"),
        ("passwd", "永豐金證券密碼"),
        ("start", datetime.now().strftime("%Y-%m-%d")),
        ("end", datetime.now().strftime("%Y-%m-%d")),
        ("stock", "2330"),
        # 這個變數是 DataBase 的
        # 因為 Shioaji 取得的資料是每分鐘,所以設成 TimeFrame.Minutes
        ("timeframe", bt.TimeFrame.Minutes),
    )
    def start(self):
        self.api = sj.Shioaji()
        self.api.login(person_id = self.p.person_id, passwd = self.p.passwd)
        contract = self.api.Contracts.Stocks[self.p.stock]
        self.kbar = self.api.kbars(contract, start = self.p.start, end = self.p.end)
        self.idx = 0
        self.dataLength = len(self.kbar.ts) # 紀錄我們取得的資料有多少筆
    def _load(self):
        # 資料會一筆一筆讀進來,如果讀到最後一筆,就跳出 False
        if (self.idx == self.dataLength):
            return False
            
        dt = datetime.utcfromtimestamp(self.kbar.ts[self.idx] / 10**9)
        self.lines.datetime[0] = date2num(dt)
        
        self.lines.open[0] = self.kbar.Open[self.idx]
        self.lines.high[0] = self.kbar.High[self.idx]
        self.lines.low[0] = self.kbar.Low[self.idx]
        self.lines.close[0] = self.kbar.Close[self.idx]
        self.lines.volume[0] = self.kbar.Volume[self.idx]
                
        self.idx += 1
        
        return True
    def stop(self):
        # 結束的時候進行登出
        self.api.logout()
        self.api = None
        
# 使用自訂的 datafeed
cerebro = bt.Cerebro()
data = ShioajiFeeds(start='2021-10-01', end='2021-10-08', stock='2303')
cerebro.resampledata(data, timeframe=bt.TimeFrame.Days)
cerebro.addstrategy(bt.Strategy)
cerebro.run()
cerebro.plot()
那如果我們要載入多筆股票的資料,以這個寫法,需要進行登入登出多次,所以可以稍微改一下,把 api 當成一個參數去執行
class ShioajiFeeds(bt.feed.DataBase):
    params = (
        ("person_id", "身份證字號"),
        ("passwd", "永豐金證券密碼"),
        ("start", datetime.now().strftime("%Y-%m-%d")),
        ("end", datetime.now().strftime("%Y-%m-%d")),
        ("stock", "2330"),
        ("timeframe", bt.TimeFrame.Minutes),
        ("api", None), # 新增一個 api 參數
    )
    def start(self):
        if self.p.api is None:
            self.api = sj.Shioaji()
            self.api.login(person_id = self.p.person_id, passwd = self.p.passwd)
        else:
            self.api = self.p.api
    # ...略
    
    def stop(self):
        if self.p.api is None:
            self.api.logout()
            
        self.api = None
在執行的時候,我們就可以先初始化 api,然後把 api 當參數傳入,就可以不用一直登入登出了
api = sj.Shioaji()
api.login(person_id = "帳號", passwd = "密碼")
data = ShioajiFeeds(start = '2021-10-01', end = '2021-10-08', stock = '2330', api = api)
# ... 略
# 結束後,記得登出
api.logout()